热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux|动静态库|动静态链接|makefile库打包|第三方库使用

文章目录何为动静态库库文件的链接静态链接静态库打包动态链接动态库打包第三方库的使用静态库的使用动态库的使用在系统层面上的动态链接理解何为动静态库静态库(.a):在程序




文章目录


    • 何为动静态库
    • 库文件的链接
      • 静态链接
      • 静态库打包
      • 动态链接
      • 动态库打包

    • 第三方库的使用
      • 静态库的使用
      • 动态库的使用

    • 在系统层面上的动态链接理解




何为动静态库



静态库(.a):在程序编译链接时将静态库二进制码拷贝到程序代码中,程序运行时不再需要外部的静态库
动态库(.so):程序在编译链接完成后,进入运行时才会去链接动态库中的二进制代码,多个程序可以共享同一个动态库
动态链接:程序运行时,操作系统将动态库从磁盘加载到内存中,这个过程叫做动态链接
动态链接生成的可执行文件包含了一个表,表中存储需要使用的动态库函数地址,而不是函数的所有代码
操作系统在进程的页表中建立虚拟地址与动态库函数地址(动态库在内存中的物理地址)之间的映射,每个进程的页表都能映射内存中的动态库,所以只需要将动态库加载到内存中,该动态库就能被多个进程同时使用,比起静态链接将函数的所有代码拷贝到程序中的方式,动态链接生成的程序占用空间更少



库文件的链接

在当前目录的上级目录下有两个文件,hello.c,hello.h
在这里插入图片描述
在这里插入图片描述
两个文件是自定义函数Print的实现和声明,在当前目录下创建test.c,该程序包含了Print函数的头文件,并且使用上级目录的自定义函数Print
在这里插入图片描述
编译该文件,但是系统报错,很显然,虽然程序有Print函数的声明(引用了上级目录的头文件…/hello.h,如果引用方式为hello.h程序报的错将是不明确的符号Print,找不到头文件hello.h,系统都不知道Print是个啥),但是没有Print的具体实现(报错信息为未定义引用的Print)。可以将Print看成一个库中的函数,除了包含头文件之外,要使用这个函数需要将目标文件与库文件进行链接,至于说为什么我们使用printf,scanf这样的库函数不需要我们链接,只需要包含对应的头文件,因为系统默认链接了这些标准库。当我们要使用第三方库时,就需要手动链接库文件

链接库文件则分为静态链接和动态链接两种方式


静态链接

先说明一个问题,对于刚才的代码,将Print函数的源文件hello.c编译生成obj目标文件,该目标文件能不能与test.c生成的目标文件test.o 一起编译生成可执行程序,如果能生成,该可执行程序能不能正确的运行?
在这里插入图片描述
编译两个源文件后,当前目录生成了两个obj目标文件
在这里插入图片描述
将两个目标文件编译生成可执行文件test并运行,可以看到Print函数被成功执行

综上所述,只要有了由需要使用函数的源文件生成的obj文件(obj文件是翻译汇编语言生成的二进制机器文件),将obj文件与main函数所在的obj文件一起编译,生成的可执行程序就能调用所需要的函数。所以,当我们需要使用其他人定义的函数时,可以将函数所在的源文件进行编译,生成obj文件,当然,如果只有obj文件也是可以的,最后只需要将obj文件一起编译就能生成可执行文件,成功使用其他人定义的函数。

而所谓的静态链接就是将库中的obj文件与main函数所在的obj文件一起编译生成可执行文件的过程,执行静态链接后,因为将库的obj文件与自己的文件一起编译,所以生成的可执行文件会包含库文件的代码,占用较大的内存。

把多个源文件编译生成的obj文件打包,并存储在一个文件下,这个文件就叫做静态库


静态库打包

使用ar指令归档需要的obj文件生成静态库,归档后生成的文件可以检索源obj文件信息。
在这里插入图片描述



具体指令为ar -rc 归档文件 原始文件


在归档文件名相同的情况下,r选项将新的归档文件替代旧的归档文件
在这里插入图片描述
当归档文件不存在时,使用c选项创建该归档文件
在这里插入图片描述
我将创建静态库的操作放在makefile文件中,以下是makefile文件的内容
在这里插入图片描述
库文件以lib开头,所以我们创建的归档文件名为libmyfirst.a,a后缀表示这是一个静态库,mymath_s.o和hello_s.o是需要进行归档的原始文件,原始文件是经过源文件的编译生成的二进制文件。

该目录此时只有mymath.c和hello.c这两个源文件,要得到obj目标文件需要使用gcc -c指令编译源文件。

静态库归档完成后,还需要头文件声明obj文件中的函数(使用一个静态库不仅需要库函数,还需要头文件告知编译器库函数的存在),所以定义一个伪目标static,该目标的依赖方法将创建两个目录,一个存放库文件,一个存放头文件,至此一个静态库文件就能向外发布了在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
用tree指令打印lib-static的目录结构,可以看到头文件和库文件都被打包放入对应的目录下了。


动态链接

和静态库的原理相同,动态库也需要生成目标文件obj,区别就是动态库的obj文件不用拷贝到可执行文件中,系统会将其加载到内存中,可执行文件只需要知道动态库在内存的地址(函数入口地址),在程序运行时,可以通过该地址调用内存中的动态库函数。


动态库打包

打包动态库需要的obj文件与打包静态库需要的obj文件不同,因为静态链接将库文件拷贝到可执行文件中,库文件的代码会在进程地址空间呈现,在把源文件编译为obj文件时,编译器将代码的链接地址写死,即采用绝对地址的方式,当程序运行时,这些obj文件最后生成的可执行文件,只能在写死的链接地址上才能正确运行代码,也就是说,运行地址必须和链接地址相同,程序才可以正常运行。但对于动态库来说,一个动态库加载到内存后,可以被多个进程链接,每个进程共享区(存储动态库的虚拟地址的区域)存储的地址都可能不相同,即运行地址可能发生变化,所以动态库在编译obj文件时,不能采用将链接代码写死的方式,否则程序无法正确运行,应该采用相对代码的方式编译源文件,不将链接地址写死。

所以,对于动态库的源文件需要使用位置无关的方式编译,对于静态库的文件使用位置有关的方式编译即可。



gcc -fPIC -c生成的obj文件为位置无关文件,也就是刚才说的采用相对代码的方式编译,
gcc -shared 源obj文件 动态库文件,该指令可以将obj文件打包成一个动态库


在这里插入图片描述


第三方库的使用


静态库的使用

可以看到我在11_16这个目录下生成了静态库和动态库,接着退出这个目录,随意进入一个目录,在该目录下创建一个源文件,引用hello.h和mymath.h这两个头文件,然后调用Print和myPow函数(两函数分别在两头文件中声明)在这里插入图片描述
gcc编译该源文件,我们发现编译器不认识hello.h这个头文件(当然mymath.h也不认识,只是hello.h在前,不认识hello.h后直接报错,后面的错误没有显示)
在这里插入图片描述
很显然系统不知道有hello.h和mymath.h这两个头文件。系统编译程序时,默认会去/usr/include这个目录下查找头文件是否存在,如果不存在,系统报错,很显然我们自定义的头文件没有在这个目录下,我们只需要将我们的头文件添加到这个目录下就行了。
在这里插入图片描述
此时再次编译源文件
在这里插入图片描述
程序还是报错,虽然程序认识Print和myPow这两个函数,但程序却找不到这两个函数的定义。显然,程序在编译时知道有Print和myPow是两个函数,但是却不知道函数的地址,因为在之后的链接过程中,函数的地址会被写入,所以编译阶段就将函数地址放空,函数地址处于一个待写入的状态,接着编译完成。但是链接时找不到这两个函数的地址,因为在链接的库中,系统找不到哪个库中含有Print和myPow这两个函数。所以目前我们可知的是,包含Print和myPow 这两个函数的库没有被链接,在链接时,我们需要告知编译器库的路径(库在哪?),以及要链接哪个库(平常使用的标准库都存储在usr/lib64这个目录下,编译器会在该默认路径下链接你需要的标准库,所有使用第三方库时,我们需要告知编译器要链接哪个库,以及库所在的路径)。



gcc -l 要链接的库名称


在这里插入图片描述
将库文件和头文件都拷贝到系统的默认路径下,就不用告知编译器头文件和库文件的路径,因为编译器会区默认路径下查找。所以我们只需要告知编译器要链接的库名称。
在这里插入图片描述
注意,链接库文件时,库文件名不用加前缀lib和后缀.a或者.so,编译器会根据命名规范自动补足名称,然后查找对应的库。在拷贝自定义的库文件和头文件到系统默认路径下后,编译源文件时指定要链接的库就能成功编译。
在这里插入图片描述
但是将自己的库文件和头文件安装到系统的默认路径下是一种不好的行为,会污染系统的库文件,除了将库文件和头文件安装到系统默认路径下,还能通过添加gcc编译时的选项使用第三方库。



gcc -L 库文件查找的路径
gcc -I 头文件查找的路径


刚才我们的编译只是指定了要链接的库,没有指定库文件和头文件的路径,因为编译器的默认行为,我们不需要特别指定路径。当我们需要链接的库不在系统默认路径下,就需要我们告知编译器库文件和头文件所在的路径。所以只要我们有静态库,并且知道库文件和头文件的路径,我们就能通过添加gcc的选项,指明对应文件路径的方式,编译一个使用第三方库的程序。
在这里插入图片描述


动态库的使用

动态库的使用也是两种方式,第一种是简单粗暴的在系统默认路径下添加库文件和头文件,最后在编译时,指定要链接的库,方法和静态库一样,这里不再阐述。

第二种方式也与静态库一样:添加编译选项,虽然可以生成可执行文件,但可执行文件却无法执行。
在这里插入图片描述
原因是程序运行时需要链接库文件,却找不到对应库文件,为什么静态库不存在这样的问题?静态链接编译生成的可执行程序将整个库都拷贝到可执行文件中,运行时不再需要静态库,而动态链接只是拷贝了一张地址表,在运行时需要通过表中地址查找并调用动态库中的函数,但现在的情况是:可执行程序知道要链接哪个动态库,却不知道动态库的路径(在磁盘中的位置),也就无法链接

在这里插入图片描述
在这里插入图片描述
解决无法链接动态库有三种方法,一是向环境变量LD_LIBRARY_PATH导入动态库路径,程序运行时,除了会在系统默认路径下查找需要链接的库,还会在该环境变量存储的路径下查找。但这种方式是一次性的,重启系统后,环境变量将会变为默认值。

另一种方式可以永久解决次问题:修改系统配置文件,/etc/ld.so.conf.d/目录下存储了许多配置文件,每个配置文件存储了一个路径,如果系统中使用了第三方库,那么程序在动态链接时,除了查找系统默认路径下的库文件,还会到/etc/ld.so.conf.d/路径下的配置文件存储的路径下查找库文件。

在/etc/ld.so.conf.d/路径下添加配置文件mylib.conf,向文件写入自定义库文件所在路径
在这里插入图片描述

然后执行sudo ldconfig,使新的配置文件生效
在这里插入图片描述

最后一个方法,与第一种简单粗暴的向系统默认目录添加文件类似,都是向系统默认路径添加文件,只是这次添加的是库文件的软链接
在这里插入图片描述
进行链接的原文件最好是绝对路径,程序编译时,只需要告知需要的头文件路径以及要链接的库名称即可,编译生成的可执行文件不会出现找不到库的错误,因为系统会去默认库文件路径下查找需要链接的库是否存在。


在系统层面上的动态链接理解

在这里插入图片描述
最开始时,可执行程序与其需要链接的动态库都存储在磁盘中,执行可执行程序后,操作系统将可执行程序与动态库从磁盘加载到内存,并建立task_struct描述可执行程序,task_struct中有一个mm_struct,不仅存储了进程的虚拟地址空间,还存储了映射虚拟地址空间与物理地址空间的页表。在虚拟地址空间中,堆区向上增长,栈区向下增长,两者相对而生,中间有一块共享区,该共享区存储的就是动态库函数的虚拟地址,动态库由磁盘加载到内存中,在物理空间上有属于自己的地址空间,页表将建立共享区与动态库的物理地址之间的映射。所以共享区存储了动态库函数的虚拟地址,程序在代码区执行代码时,如果遇到了动态库函数,会通过函数地址(虚拟地址)跳转到共享区,由于地址是虚拟地址,进程将通过页表的映射得到动态库函数的物理地址,进程将执行处于内存上的动态库函数,执行完毕,再从共享区跳转回代码区。

所以,当一个进程需要动态链接时,需要将动态库从磁盘加载到内存中,对于第三方库,如果系统不知道库文件在磁盘的哪个位置(不知道库文件路径),那么系统就无法找到库文件,更不用说将其加载到内存中,程序也就无法与库进行链接。所以对于第三方库文件没有存储在系统默认路径下的情况,可执行程序虽然知道要链接哪个库文件,但不知道库文件的位置,也就无法加载库文件,程序中的库函数没有映射对象,函数只有声明,没有定义,当然无法调用函数







推荐阅读
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • 本文探讨了在Linux 2.6内核中实现进程隐藏的技术方法与实践。通过分析系统调用 `sys_getdents` 的工作原理,提出了一种有效的方法来隐藏指定的进程。该方法通过对内核模块进行修改,拦截并过滤掉目标进程的相关信息,从而在常用的进程查看命令(如 `ps` 和 `top`)中无法显示这些隐藏的进程。实验结果表明,该方法在实际应用中具有较高的隐蔽性和稳定性。 ... [详细]
  • 在进行网络编程时,准确获取本地主机的IP地址是一项基本但重要的任务。Winsock作为20世纪90年代初由Microsoft与多家公司共同制定的Windows平台网络编程接口,为开发者提供了一套高效且易用的工具。通过Winsock,开发者可以轻松实现网络通信功能,并准确获取本地主机的IP地址,从而确保应用程序在网络环境中的稳定运行。此外,了解Winsock的工作原理及其API函数的使用方法,有助于提高开发效率和代码质量。 ... [详细]
  • 进程(Process)是指计算机中程序对特定数据集的一次运行活动,是系统资源分配与调度的核心单元,构成了操作系统架构的基础。在早期以进程为中心的计算机体系结构中,进程被视为程序的执行实例,其状态和控制信息通过任务描述符(task_struct)进行管理和维护。本文将深入探讨进程的概念及其关键数据结构task_struct,解析其在操作系统中的作用和实现机制。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • BZOJ4240 Gym 102082G:贪心算法与树状数组的综合应用
    BZOJ4240 Gym 102082G 题目 "有趣的家庭菜园" 结合了贪心算法和树状数组的应用,旨在解决在有限时间和内存限制下高效处理复杂数据结构的问题。通过巧妙地运用贪心策略和树状数组,该题目能够在 10 秒的时间限制和 256MB 的内存限制内,有效处理大量输入数据,实现高性能的解决方案。提交次数为 756 次,成功解决次数为 349 次,体现了该题目的挑战性和实际应用价值。 ... [详细]
  • 在CentOS上部署和配置FreeSWITCH
    在CentOS系统上部署和配置FreeSWITCH的过程涉及多个步骤。本文详细介绍了从源代码安装FreeSWITCH的方法,包括必要的依赖项安装、编译和配置过程。此外,还提供了常见的配置选项和故障排除技巧,帮助用户顺利完成部署并确保系统的稳定运行。 ... [详细]
  • 结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法
    结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法 ... [详细]
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • C++ 进阶:类的内存布局与虚函数类的实现细节
    C++ 进阶:类的内存布局与虚函数类的实现细节 ... [详细]
  • BZOJ1034 详细解析与算法优化
    本文深入解析了BZOJ1034问题,并提出了优化算法。通过借鉴广义田忌赛马的贪心策略,当己方当前最弱的马优于对方最弱的马时进行匹配;同样地,若己方当前最强的马优于对方最强的马,也进行匹配。此方法在保证胜率的同时,有效提升了算法效率。 ... [详细]
  • 本文深入探讨了 Python Watchdog 库的使用方法和应用场景。通过详细的代码示例,展示了如何利用 Watchdog 监控文件系统的变化,包括文件的创建、修改和删除等操作。文章不仅介绍了 Watchdog 的基本功能,还探讨了其在实际项目中的高级应用,如日志监控和自动化任务触发。读者将能够全面了解 Watchdog 的工作原理及其在不同场景下的应用技巧。 ... [详细]
author-avatar
nuabolalalala5_760
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有